#include <types.h>
#include <stdio.h>
#include <wait.h>
#include <sync.h>
#include <proc.h>
#include <sched.h>
#include <dev.h>
#include <vfs.h>
#include <iobuf.h>
#include <inode.h>
#include <unistd.h>
#include <error.h>
#include <assert.h>

#define STDIN_BUFSIZE				4096

static char stdin_buffer[STDIN_BUFSIZE];
static off_t p_rpos, p_wpos;
static wait_queue_t __wait_queue, *wait_queue = &__wait_queue;

void
dev_stdin_write(char c) {
	bool intr_flag;
	if (c != '\0') {
		local_intr_save(intr_flag);
		{
			stdin_buffer[p_wpos % STDIN_BUFSIZE] = c;
			if (p_wpos - p_rpos < STDIN_BUFSIZE) {
				p_wpos ++;
			}
			if (!wait_queue_empty(wait_queue)) {
				wakeup_queue(wait_queue, WT_KBD, 1);
			}
		}
		local_intr_restore(intr_flag);
	}
}

static int
dev_stdin_read(char *buf, size_t len) {
	int ret = 0;
	bool intr_flag;
	local_intr_save(intr_flag);
	{
		for (; ret < len; ret ++, p_rpos ++) {
		try_again:
			if (p_rpos < p_wpos) {
				*buf ++ = stdin_buffer[p_rpos % STDIN_BUFSIZE];
			}
			else {
				wait_t __wait, *wait = &__wait;
				wait_current_set(wait_queue, wait, WT_KBD);
				local_intr_restore(intr_flag);

				schedule();

				local_intr_save(intr_flag);
				wait_current_del(wait_queue, wait);
				if (wait->wakeup_flags == WT_KBD) {
					goto try_again;
				}
				break;
			}
		}
	}
	local_intr_restore(intr_flag);
	return ret;
}

static int
stdin_open(struct device *dev, uint32_t open_flags) {
	if (open_flags != O_RDONLY) {
		return -E_INVAL;
	}
	return 0;
}

static int
stdin_close(struct device *dev) {
	return 0;
}

static int
stdin_io(struct device *dev, struct iobuf *iob, bool write) {
	if (!write) {
		int ret;
		if ((ret = dev_stdin_read(iob->io_base, iob->io_resid)) > 0) {
			iob->io_resid -= ret;
		}
		return ret;
	}
	return -E_INVAL;
}

static int
stdin_ioctl(struct device *dev, int op, void *data) {
	return -E_INVAL;
}

static void
stdin_device_init(struct device *dev) {
	dev->d_blocks = 0;
	dev->d_blocksize = 1;
	dev->d_open = stdin_open;
	dev->d_close = stdin_close;
	dev->d_io = stdin_io;
	dev->d_ioctl = stdin_ioctl;

	p_rpos = p_wpos = 0;
	wait_queue_init(wait_queue);
}

void
dev_init_stdin(void) {
	struct inode *node;
	if ((node = dev_create_inode()) == NULL) {
		panic("stdin: dev_create_node.\n");
	}
	stdin_device_init(vop_info(node, device));

	int ret;
	if ((ret = vfs_add_dev("stdin", node, 0)) != 0) {
		panic("stdin: vfs_add_dev: %e.\n", ret);
	}
}

